package demo.db.tsdb.prometheus;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import demo.db.tsdb.prometheus.vo.PromRange;
import demo.java.net.HttpDemo;
import demo.java.net.URLDemo;
import demo.vo.exp.ServiceException;
import demo.vo.http.ResponseVo;
import demo.vo.tsdb.DataPoint;
import demo.vo.tsdb.TsdbData;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Prometheus 查询地址
 */
public class PrometheusApi {

    private static final Logger logger = LoggerFactory.getLogger(PrometheusApi.class);

    public static final String STATUS = "status";
    public static final String DATA = "data";
    public static final String METRIC = "metric";
    public static final String VALUES = "values";
    public static final String RESULT = "result";
    public static final String STATUS_SUCCESS = "success";

    private String host;

    public PrometheusApi() {
    }

    public PrometheusApi(String host) {
        this.host = host;
    }

    /**
     * 瞬时数据查询
     *
     * @param promQL PromQL表达式
     * @param time   用于指定用于计算PromQL的时间戳。可选参数，默认情况下使用当前系统时间。
     * @return
     * @throws IOException
     * @throws ServiceException
     */
    public JSONObject query(String promQL, Long time)
            throws IOException, ServiceException {
        promQL = URLDemo.urlEncode(promQL);
        String query = "/api/v1/query?query=" + promQL;
        if (time != null) {
            query += "&time=" + DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(time));
        }
        String url = host + query;
        ResponseVo responseVo = HttpDemo.get(url, 3000, 9000);
        logger.debug("query return {}", responseVo);
        if (responseVo.getCost() > 3000) {
            logger.warn("query {} cost {} ms", promQL, responseVo.getCost());
        }
        if (responseVo.isHttpStatus2XX()) {
            return JSON.parseObject(responseVo.getContent());
        }
        throw new ServiceException(responseVo.toString());
    }

    /**
     * 区间数据查询。当使用QUERY_RANGE API查询PromQL表达式时，返回结果一定是一个区间向量。
     * <p>
     * 在QUERY_RANGE API中PromQL只能使用瞬时向量选择器类型的表达式。
     *
     * @param promQL PromQL表达式。
     * @param start  起始时间
     * @param end    结束时间
     * @param step   查询步长
     * @return
     * @throws IOException
     * @throws ServiceException
     */
    public JSONObject queryRange(String promQL, long start, long end, String step)
            throws IOException, ServiceException {
        promQL = URLDemo.urlEncode(promQL);
        String queryPattern = "/api/v1/query_range?query=%s&start=%s&end=%s&step=%s";
        String startTime = DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(start));
        String endTime = DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(end));
        String url = host + String.format(queryPattern, promQL, startTime, endTime, step);
        ResponseVo responseVo = HttpDemo.get(url, 3000, 20_000);
        logger.debug("queryRange return {}", responseVo);
        if (responseVo.getCost() > 3000) {
            logger.warn("queryRange {} cost {} ms", promQL, responseVo.getCost());
        }
        if (responseVo.isHttpStatus2XX()) {
            return JSON.parseObject(responseVo.getContent());
        }
        throw new ServiceException(responseVo.toString());
    }


    /**
     * 区间数据查询。当使用QUERY_RANGE API查询PromQL表达式时，返回结果一定是一个区间向量。
     * <p>
     * 在QUERY_RANGE API中PromQL只能使用瞬时向量选择器类型的表达式。
     *
     * @param promQL PromQL表达式。
     * @param start  起始时间
     * @param end    结束时间
     * @param step   查询步长
     * @return
     * @throws IOException
     * @throws ServiceException
     */
    public PromRange queryRange2(String promQL, long start, long end, String step)
            throws IOException, ServiceException {
        JSONObject json = queryRange(promQL, start, end, step);
        return map(json);
    }


    public static PromRange map(JSONObject json) throws ServiceException {
        PromRange res = new PromRange();
        String status = json.getString(PrometheusApi.STATUS);
        res.setStatus(status);
        JSONObject dataJson = json.getJSONObject(PrometheusApi.DATA);
        String resultType = dataJson.getString("resultType");
        PromRange.Data data = new PromRange.Data();
        data.setResultType(resultType);

        JSONArray resultArray = dataJson.getJSONArray(PrometheusApi.RESULT);
        List<TsdbData> result = new ArrayList<>(resultArray.size());
        for (int i = 0; i < resultArray.size(); i++) {
            JSONObject element = resultArray.getJSONObject(i);
            Map<String, String> tags = element.getObject(PrometheusApi.METRIC,
                    new TypeReference<Map<String, String>>() {
                    });
            JSONArray values = element.getJSONArray(PrometheusApi.VALUES);
            List<DataPoint> dps = new ArrayList<>(values.size());
            for (int j = 0; j < values.size(); j++) {
                JSONArray a = values.getJSONArray(j);
                DataPoint vo = new DataPoint(a.getLongValue(0) * 1000,
                        a.getDoubleValue(1));
                dps.add(vo);
            }
            TsdbData tsdbData = new TsdbData();
            tsdbData.setDps(dps);
            tsdbData.setTags(tags);
            result.add(tsdbData);
        }
        data.setResult(result);
        res.setData(data);
        return res;
    }

    /**
     * 生成 Prometheus 数据格式的字符串
     */
    public static String formatMetric(String metricName, double value, long timestamp, Map<String, String> labels) {
        StringBuilder sb = new StringBuilder();

        // 添加指标名称和数值
        sb.append(metricName).append("{");

        // 添加标签
        if (labels != null && !labels.isEmpty()) {
            for (Map.Entry<String, String> entry : labels.entrySet()) {
                sb.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\",");
            }
            // 删除最后一个多余的逗号
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append("} ").append(value).append(" ").append(timestamp).append("\n");

        return sb.toString();
    }

    /**
     * <blockquote><pre>
     *     # 单条数据格式，多个数据以换行符分隔
     *     metric_name{label1="value1", label2="value2"} <value> <timestamp>
     * </pre></blockquote>
     * 导入数据到Prometheus
     *
     * @param data
     */
    public void importData(List<String> data) {
        if (data == null || data.isEmpty()) {
            return;
        }
        String body = String.join("\n", data);
        String url = host + "/api/v1/import/prometheus";
        System.out.println(body);
        HttpResponse<String> response = Unirest.post(url)
                .header("Content-Type", "application/x-www-form-urlencoded") // 设置Content-Type
                .body(body) // 设置请求体
                .asString();
        logger.debug("importData return {}", JSON.toJSONString(response, true));
        if (!response.isSuccess()) {
            logger.error("Failed to import data: {}", JSON.toJSON(response));
            throw new RuntimeException("Failed to import data: " + JSON.toJSON(response));
        }
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }
}
